package com.gaojinqi.base.core.jpa.util;

import com.gaojinqi.base.common.util.FieldUtil;
import com.gaojinqi.base.common.util.StringUtil;
import com.gaojinqi.base.core.jpa.annotation.*;
import com.gaojinqi.base.core.jpa.constant.SqlConstant;
import com.gaojinqi.base.core.jpa.domain.AnnotationQueryEntity;
import com.gaojinqi.base.core.jpa.domain.SimpleJpaQuery;
import com.gaojinqi.base.core.jpa.domain.SimpleSpecification;
import com.gaojinqi.base.core.jpa.enums.PropertyOperatorEnum;
import com.gaojinqi.base.core.jpa.group.Default;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.jpa.domain.Specification;

import java.lang.reflect.Field;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Function;

import static com.gaojinqi.base.common.util.FieldUtil.NON_STATIC_FILTER;
import static com.gaojinqi.base.common.util.FieldUtil.TRANSIENT_FILTER;

/**
 * 用于解析注解，并生成Specification
 *
 * @author gaojinqi
 * @version 1.0
 * @since 2020年06月19日
 */
@Slf4j
public class AnnotationQueryUtil {

    /**
     * 字段名转换器，默认不转换
     */
    private static Function<String, String> FIELD_NAME_CONVERTER = fieldName -> fieldName;

    /**
     * 设置字段名转换器
     */
    public static void setFieldNameConverter(Function<String, String> fieldNameConverter) {
        if (fieldNameConverter == null) {
            throw new RuntimeException("字段转换器不能为空");
        }
        FIELD_NAME_CONVERTER = fieldNameConverter;
    }

    /**
     * 解析传入实体的字段上的注解    <br/>
     * <p>
     * 默认不加任何注解时，如果是字符串类型则使用模糊匹配，其他类型则使用全匹配 <br/>
     * 获取的字段是非静态非瞬时的   <br/>
     * <p>
     * 对于相同的字段使用不同查询方式的场景，可设置分组来指定使用哪种查询方式，默认不设置使用 {@link Default}
     * 设置分组的方式：  优先级从上到下越来越低           <br/>
     * 1.继承 {@link AnnotationQueryEntity} , 并调用setGroups方法设置分组
     * 2.{@link QueryGroupHelper} setGroups方法可以设置分组
     * <p>
     */
    public static <T> Specification<T> getSpecification(Object bean) {
        SimpleJpaQuery<?> jpaQuery = new SimpleJpaQuery<>();
        Class<?> beanClass = bean.getClass();
        // 获取分组
        Set<Class<?>> groups = getGroups(bean);
        // 获取非静态非瞬时字段
        Field[] fields = FieldUtil.getFieldsByFilter(beanClass,
                NON_STATIC_FILTER.and(TRANSIENT_FILTER.negate()));
        // 解析
        for (Field field : fields) {
            field.setAccessible(true);
            addCondition(field, jpaQuery, bean, groups);
        }
        QueryGroupHelper.clearGroups(bean);
        return new SimpleSpecification<>(jpaQuery);
    }


    /**
     * 根据字段添加查询条件
     *
     * @param field    字段
     * @param jpaQuery 查询条件封装类
     * @param bean     对象
     * @param groups   指定分组
     */
    private static void addCondition(Field field, SimpleJpaQuery<?> jpaQuery, Object bean, Set<Class<?>> groups) {
        Object fieldValue = getFieldValue(bean, field);
        // 如果是空则不加入到查询条件里
        if (fieldValue == null) {
            return;
        }
        // 字段名，可转换
        String fieldName = convertFieldName(field);
        // 先解析字段上的注解
        if (field.isAnnotationPresent(Containing.class)) {
            Containing annotation = field.getAnnotation(Containing.class);
            if (sameGroup(groups, annotation.groups())) {
                String prefix = SqlConstant.WILDCARD_ANY;
                String suffix = SqlConstant.WILDCARD_ANY;
                jpaQuery.addNormalCondition(PropertyOperatorEnum.LIKE, fieldName, prefix + fieldValue + suffix);
                return;
            }
        }
        if (field.isAnnotationPresent(Equals.class)) {
            Equals annotation = field.getAnnotation(Equals.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.EQUAL, fieldName, fieldValue);
                return;
            }
        }
        if (field.isAnnotationPresent(StartingWith.class)) {
            StartingWith annotation = field.getAnnotation(StartingWith.class);
            if (sameGroup(groups, annotation.groups())) {
                String suffix = SqlConstant.WILDCARD_ANY;
                jpaQuery.addNormalCondition(PropertyOperatorEnum.LIKE, fieldName, fieldValue + suffix);
                return;
            }
        }
        if (field.isAnnotationPresent(EndingWith.class)) {
            EndingWith annotation = field.getAnnotation(EndingWith.class);
            if (sameGroup(groups, annotation.groups())) {
                String prefix = SqlConstant.WILDCARD_ANY;
                jpaQuery.addNormalCondition(PropertyOperatorEnum.LIKE, fieldName, prefix + fieldValue);
                return;
            }
        }
        if (field.isAnnotationPresent(Like.class)) {
            Like annotation = field.getAnnotation(Like.class);
            if (sameGroup(groups, annotation.groups())) {
                String prefix = StringUtil.asString(annotation.prefix());
                String suffix = StringUtil.asString(annotation.suffix());
                jpaQuery.addNormalCondition(PropertyOperatorEnum.LIKE, fieldName, prefix + fieldValue + suffix);
                return;
            }
        }
        if (field.isAnnotationPresent(LessThan.class)) {
            LessThan annotation = field.getAnnotation(LessThan.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.LESS_THAN, fieldName, fieldValue);
                return;
            }
        }
        if (field.isAnnotationPresent(LessThanEqual.class)) {
            LessThanEqual annotation = field.getAnnotation(LessThanEqual.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.LESS_THAN_AND_EQUAL, fieldName, fieldValue);
                return;
            }
        }
        if (field.isAnnotationPresent(GreaterThan.class)) {
            GreaterThan annotation = field.getAnnotation(GreaterThan.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.GREATER_THAN, fieldName, fieldValue);
                return;
            }
        }
        if (field.isAnnotationPresent(GreaterThanEqual.class)) {
            GreaterThanEqual annotation = field.getAnnotation(GreaterThanEqual.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.GREATER_THAN_AND_EQUAL, fieldName, fieldValue);
                return;
            }
        }
        if (field.isAnnotationPresent(In.class)) {
            In annotation = field.getAnnotation(In.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.IN, fieldName, fieldValue);
                return;
            }
        }
        if (field.isAnnotationPresent(NotLike.class)) {
            NotLike annotation = field.getAnnotation(NotLike.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.NOT_LIKE, fieldName, fieldValue);
                return;
            }
        }
        if (field.isAnnotationPresent(NotEquals.class)) {
            NotEquals annotation = field.getAnnotation(NotEquals.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.NOT_EQUAL, fieldName, fieldValue);
                return;
            }
        }
        if (field.isAnnotationPresent(NotIn.class)) {
            NotIn annotation = field.getAnnotation(NotIn.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.NOT_IN, fieldName, fieldValue);
                return;
            }
        }
        // 如果字段不存在注解则解析类上的注解
        Class<?> beanClass = bean.getClass();
        String prefix = SqlConstant.WILDCARD_ANY;
        String suffix = SqlConstant.WILDCARD_ANY;
        if (beanClass.isAnnotationPresent(Containing.class)) {
            Containing annotation = field.getAnnotation(Containing.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.LIKE, fieldName, prefix + fieldValue + suffix);
                return;
            }
        }
        if (beanClass.isAnnotationPresent(Equals.class)) {
            Equals annotation = field.getAnnotation(Equals.class);
            if (sameGroup(groups, annotation.groups())) {
                jpaQuery.addNormalCondition(PropertyOperatorEnum.EQUAL, fieldName, fieldValue);
                return;
            }
        }
        // 默认解析，如果是字符串类型则使用模糊匹配，其他类型则使用全匹配
        if (isString(field)) {
            jpaQuery.addNormalCondition(PropertyOperatorEnum.LIKE, fieldName, prefix + fieldValue + suffix);
        } else {
            jpaQuery.addNormalCondition(PropertyOperatorEnum.EQUAL, fieldName, fieldValue);
        }
    }

    /**
     * 判断bean跟注解是否有相同的分组class
     */
    private static boolean sameGroup(Set<Class<?>> groups, Class<?>... annotationGroups) {
        Set<Class<?>> annotationGroupSet = getAnnotationGroups(annotationGroups);
        for (Class<?> group : annotationGroupSet) {
            if (groups.contains(group)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 获取bean上的分组
     */
    private static Set<Class<?>> getGroups(Object bean) {
        // 读取 AnnotationQueryEntity 实现类上的分组
        if (bean instanceof AnnotationQueryEntity) {
            Set<Class<?>> beanGroupSet = ((AnnotationQueryEntity) bean).getGroups();
            if (beanGroupSet != null && !beanGroupSet.isEmpty()) {
                return beanGroupSet;
            }
        }
        // 如果找不到则从 QueryGroupHelper 上查找
        Set<Class<?>> groupSet = QueryGroupHelper.getGroups(bean);
        if (groupSet != null && !groupSet.isEmpty()) {
            return groupSet;
        }
        // 如果找不到则使用默认分组
        groupSet = new LinkedHashSet<>();
        groupSet.add(Default.class);
        return groupSet;
    }

    /**
     * 获取注解上的分组
     */
    private static Set<Class<?>> getAnnotationGroups(Class<?>... annotationGroups) {
        Set<Class<?>> annotationGroupSet = new LinkedHashSet<>();
        if (annotationGroups == null || annotationGroups.length <= 0) {
            annotationGroupSet.add(Default.class);
            return annotationGroupSet;
        }
        Collections.addAll(annotationGroupSet, annotationGroups);
        return annotationGroupSet;
    }

    /**
     * 根据数据库转行字段名
     */
    private static String convertFieldName(Field field) {
        // 判断是否使用注解自定义字段名
        if (field.isAnnotationPresent(QueryField.class)) {
            QueryField annotation = field.getAnnotation(QueryField.class);
            if (!"".equals(annotation.fieldName())) {
                return annotation.fieldName();
            }
        }
        // 否则使用转换器的转换后的列名
        return FIELD_NAME_CONVERTER.apply(field.getName());
    }

    /**
     * 判断该字段是否是字符串类型
     */
    private static boolean isString(Field field) {
        Class<?> fieldClass = field.getType();
        return String.class.isAssignableFrom(fieldClass) || StringBuilder.class.isAssignableFrom(fieldClass) || StringBuffer.class.isAssignableFrom(fieldClass);
    }

    private static Object getFieldValue(Object bean, Field field) {
        Object fieldValue;
        try {
            fieldValue = field.get(bean);
        } catch (IllegalAccessException e) {
            log.error(String.format("注解查询解析异常，beanClass = %s，fieldName = %s，errorMsg = %s",
                    bean.getClass().getName(), field.getName(), e.getMessage()), e);
            throw new RuntimeException();
        }
        return fieldValue;
    }

}
